图像处理20210228

151次阅读
没有评论

共计 7868 个字符,预计需要花费 20 分钟才能阅读完成。

提醒:本文最后更新于 2024-10-23 16:27,文中所关联的信息可能已发生改变,请知悉!

opencv 基础

该过程主要通过实际操作完成

素材

选取合适的高清图片,通过截屏生成新图片降低图片质量,将新的低质量图片命名为 text1.png 保存在 python 脚本的目录中

代码环境

python 解释器:anaconda3/python3.8
编译器:pycharm
编码:utf-8

代码

为了方便测试,只使用了一个脚本测试,学习笔记和部分运行结果也通过注释的方式简单加入

去除注释 # 即可运行

# python 解释器:anaconda3/python3.8
# 编译器:pycharm
# utf-8

# 一些测试过程以注释方式保留,以便以后查看

# 导入所需要的库,并给以简洁的名称
import numpy as np
import cv2 as cv

# 1
# 按指定方式读取图像
img = cv.imread('test1.png', 1)    # 该步骤类似于 C 语言的文件指针
# 第一个参数为图片路径,不能含有中文等不兼容字符,否则报错,这里没办法只好使用了相对路径
# 第二个参数代表读取方式
# 1:加载彩色图像。任何图像的透明度都会被忽视。它是默认标志。# 0:以灰度模式加载图像
# -1:加载图像,包括 alpha 通道

# 2
# show 图像
# cv.imshow('test1', img)  # 第一个参数是窗口名称。第二个参数是我们的对象。# 
# cv.waitKey(0)     # 以 0 为参数时,无限制等待用户按下任意键
# cv.destroyAllWindows()     # 销毁窗口

# 3
# 访问和修改像素
# px = img[100, 100]    # 该值与图像读入方式有关
# print(px)
# img[100, 100] = 255                 # 灰度
# img[100, 100] = [255, 255, 255]     # BGR
# print(img[100, 100])

# 查阅资料了解到上面的方法的效率并不是很高
# 可以使用 Numpy 数组方法 array.item()和 array.itemset()
# 经测试,因为某些未知原因,导致运行错误,这里先略去,以后再 debug

# 4
# 访问图像属性
# print(img.shape)    # 访问图像形状
# 以灰度图像读入时,输出(674, 1200),仅返回行和列
# 以 BGR 读入时,输出(674, 1200, 3),多返回一位数,表示通道数

# print(img.size)    # 访问图像像素总数
# 以灰度读入时,输出 808800
# 以 BGR 读入时,输出 2426400

# print(img.dtype)    # 访问图像数据类型
# 输出 uint8
# img.dtype 在调试时非常重要,因为 OpenCV-Python 代码中的大量错误是由无效的数据类型引起的。# 5
# 拆分和合并图像通道
# B, G, R = cv.split(img)    # 此时 img 由 BGR 方式读入,才可进行此操作
# print(B.shape)    # 输出(674, 1200),与原图像数据相比仅缺少通道数,这表明拆分成功

# img = cv.merge((B, G, R))    # 进行这一步操作时,图像通过 BGR 方式读入,且上面的“B, G, R = cv.split(img)”需要先执行
# print(img.shape)    # 输出(674, 1200, 3),与原图像数据完全相同,这表明合并成功

# 6
# 图像加法
# x = np.uint8([250])
# y = np.uint8([10])
# print(cv.add(x, y))    # 输出[[255]] 原理:opencv 的加法采用饱和运算 250+10=260->255
# print(x + y)           # 输出[4] 原理:numpy 的加法采用模运算 (250+10)%256=4
# 两者相比较,使用时应该选用 opencv 的加法

opencv 进阶

注:这部分内容是有针对性的学习,暂时用不到的就没有学

性能衡量和提升技术

该部分内容,我只简单提取了 cv.useOptimized()cv.setUseOptimized()两条命令

对于部分操作的运行速度,优化会比不优化快两倍,所以我觉得有必要注意

检查是否使用优化

import cv2 as cv               # 导入 opencv 库,简化为 cv
cv.useOptimized()              # 检查是否使用 opencv 优化,该函数值为 Ture 或者 False
print(cv.useOptimized())       # 打印该函数值,判断是否启用优化

启用 / 禁用优化

import cv2 as cv               # 导入 opencv 库,简化为 cv
cv.setUseOptimized(Ture)       # 启用优化
cv.setUseOptimized(False)      # 禁用优化

其他性能优化技术

这部分内容暂时不学习,但是先做个摘记

有几种技术和编码方法可以充分利用 Python 和 Numpy 的最大性能。这里要注意的主要事情是,首先尝试以一种简单的方式实现算法。一旦它运行起来,分析它,找到瓶颈并优化它们。

1. 尽量避免在 Python 中使用循环,尤其是双 / 三重循环等。它们本来就很慢。

2. 由于 Numpy 和 OpenCV 已针对向量运算进行了优化,因此将算法 / 代码向量化到最大程度。

3. 利用缓存一致性。

4. 除非需要,否则切勿创建数组的副本。尝试改用视图。数组复制是一项昂贵的操作。

即使执行了所有这些操作后,如果你的代码仍然很慢,或者不可避免地需要使用大循环,请使用 Cython 等其他库来使其更快。

图像梯度

OpenCV 提供三种类型的梯度滤波器或高通滤波器,即 Sobel,Scharr 和 Laplacian

这是三个可以直接用的函数,暂时没搞清楚它们的原理,去了解了一下使用效果,进行实测后失败,原因未知,这里记录一下

官方使用的效果图如下:

图像处理 20210228

可以看出 Laplacian 方法较为优秀

Canny 边缘检测

cv.Canny()方法

这个算法,或许会对我的工作有借鉴意义,虽然思路上有很大不同,我把这种方法分类为矢量方法,而我认为我所需要做的工作属于标量方法。我个人的观点是:矢量方法更需要想象能力,标量方法更需要精密的思维;而有些东西是共通的,比如下面讲到的降噪、阈值思想

降噪

由于边缘检测容易受到图像中噪声的影响,因此第一步是使用 5x5 高斯滤波器消除图像中的噪声。

和上面的“图像梯度”一样,也需要降噪,由此可见,降噪在图像处理中是很重要的

查找图像的强度梯度

使用 Sobel 核在水平和垂直方向上对平滑的图像进行滤波,以在水平方向 (Gx) 和垂直方向 (Gy) 上获得一阶导数。渐变方向始终垂直于边缘。将其舍入为代表垂直,水平和两个对角线方向的四个角度之一。

非极大值抑制

在获得梯度大小和方向后,将对图像进行全面扫描,以去除可能不构成边缘的所有不需要的像素。为此,在每个像素处,检查像素是否是其在梯度方向上附近的局部最大值。

效果是能提取出细边

磁滞阈值

确定哪些边缘全部是真正的边缘,哪些不是。为此,我们需要两个阈值 minValmaxVal。强度梯度大于 maxVal 的任何边缘必定是边缘,而小于 minVal 的那些边缘必定是非边缘,因此将其丢弃。介于这两个阈值之间的对象根据其连通性被分类为边缘或非边缘。如果将它们连接到“边缘”像素,则将它们视为边缘的一部分。否则,它们也将被丢弃。

阈值说白了,就是人为搞个界限,挑选出较明显的边缘和非边缘,用于简化计算

另外,还有些特殊情况:边缘 A 在 maxVal 之上,因此被视为“确定边缘”。尽管边 C 低于 maxVal,但它连接到边 A,因此也被视为有效边,我们得到了完整的曲线。但是边缘 B 尽管在 minVal 之上并且与边缘 C 处于同一区域,但是它没有连接到任何“确保边缘”,因此被丢弃。因此,非常重要的一点是我们必须相应地选择 minValmaxVal 以获得正确的结果。

在边缘为长线的假设下,该阶段还消除了小像素噪声。因此,我们最终得到的是图像中的强边缘。

小总结

进阶部分就暂时学到这里(内容还有很多啊,但为了开始尝试一下自己的不一样的图像算法,还是先停下),不得不恭维一下 opencv 库,网上都说这是一个强大的库,但仅仅一个形容词“强大”,怎么能让我了解它,难不成仅仅是通过它的体积大,下载慢?

看过一些函数之后,才开始发自内心赞叹,比如单单拿出一个函数,让我封装起来,提供大部分语言的接口,供给通用场景使用,这就不是现在的我能做到的了,或许有一天我也可以吧。抽空得多看看这些函数的漂亮的源码。同时在接下来的任务中,我也期待着 opencv 能给我带来的新的震撼,或许会是仰止弥高,钻之弥坚。

numpy 的应用

OpenCV-Python Tutorials 中也常提到 numpy 库,即便在根本没用到它的代码示例中,也会来一行import numpy

在实践中也发现,numpy 能极大提高代码编写的效率;而查阅资料后发现,numpy 对数据的索引效率远高于不使用它的情况,所以 numpy 也是图像处理中的一大利器。

但是由于时间原因,暂时不像学习 opencv 一样对 numpy 进行系统的学习,这里就记录一些用法

结构体数组

import numpy as np
# 建立结构体类型
Mytype = np.dtype({'names': ['value', 'noise', 'part'],    # value 像素值,noise 噪声值,part 分区(1/2)'formats': ['i', 'i', 'i']    # 这里都采用整型(numpy 对于变量的范围和类型要求严格)})
# 新建结构体数组,下面的代码能直接新建自定义类型的,初始化的数组
array = np.zeros((m, n), dtype=Mytype)    #“(m, n)”定义数组的形式,这里为二维数组,m 行 n 列

numpy 数组排序

由于 numpy 建立的数组可以很复杂,所以 numpy 的排序函数的参数也很多

import numpy
numpy.sort(a, axis=-1, kind=None, order=None)
# a : 要排序的数组
# axis:按什么轴进行排序,默认按最后一个轴进行排序
# kind:排序方法,默认是快速排序
# order : 当数组定义了字段属性时,可以按照某个属性进行排序

图像处理实战

测试 1

思路

将素材图片(同附件)读入,然后通过两种划分方法的遍历比较,得出噪声值(次数),修改像素操作(容易实现)及其他特殊情况的优化先不考虑

图像处理 20210228

代码

(同附件)

# python 解释器:anaconda3/python3.8
# 编译器:pycharm
# utf-8

import numpy as np
import cv2 as cv
import heapq

# 创建管理像素噪声值及分区的结构体
Pixel_type = np.dtype({'names': ['value', 'noise', 'part'],  # value 像素值,noise 噪声值,part 分区(1/2)'formats': ['i', 'i', 'i']
})

# 判断区分度
def judge_division_degree(array):
    p1 = []
    p2 = []
    for i in range(0, len(array)):
        if array[i]['part'] == 1:
            p1.append(array[i]['value'])
        elif array[i]['part'] == 0:
            p2.append(array[i]['value'])
    # 防止列表为空
    if len(p1) == 0 or len(p2) == 0:
        return False
    expected_degree = 0
    if min(p2) - max(p1) <= expected_degree:
        return False
    else:
        return True

# 划分方法一(不考虑位置)def division_method1(array):
    # 使 p 数组为有序集(p 数组为一维数组,无需考虑轴)
    p = np.sort(array, order='value')
    # p1 为 p 中相邻两数之差的数组
    p1 = []
    for i in range(0, len(p) - 1):
        p1.append(p[i + 1]['value'] - p[i]['value'])
    # 找出最大差值的下标
    max_index = p1.index(max(p1))
    # 建立较小数下标不重复数组
    small_indexs = list(set(heapq.nsmallest(max_index + 1, p['value'])))
    # 将较小数划分到区域一,标记为“1”,反之,标记仍为“0”的在区域二
    for m in range(0, len(small_indexs)):
        for n in range(0, len(array)):
            if small_indexs[m] == array[n]['value']:
                array[n]['part'] = 1
    # 返回被标记好的数组
    return array

# 划分方法二(考虑位置)def division_method2(array):
    # 建立数组 p,存放顺时针方向像素的差值(前 - 后)
    p = []
    for i in range(0, len(array) - 1):
        p.append(array[i]['value'] - array[i + 1]['value'])
    p.append(array[-1]['value'] - array[0]['value'])
    min_index = p.index(min(p))
    max_index = p.index(max(p))
    # 始终将较小部分的数标记为 1
    if min_index > max_index:
        for i in range(max_index, min_index + 1):
            array[i]['part'] = 1
    elif min_index < max_index:
        for i in range(0, len(p)):
            array[i]['part'] = 1
        for i in range(min_index, max_index + 1):
            array[i]['part'] = 0
    # 返回被标记好的数组
    return array

# 检查噪声点,传入的参数为单色图像通道
def noise_check(image_channel):
    # 锁定图像边界
    (x, y) = image_channel.shape
    # 建立二维通道数组
    b = np.zeros((x, y), dtype=Pixel_type)
    # 先建立数组
    for i in range(0, x):
        for j in range(0, y):
            # 存入像素值,并初始化噪声值和分区
            b[i][j] = (image_channel[i][j], 0, 0)
    # 因为接下来的操作需要提取一个个完整的九宫格
    # 所以遍历像素点时,图像最边缘的像素点永不成为中心像素点
    # 即:从 1 开始到最大值 - 1 结束
    for i in range(1, x - 1):
        for j in range(1, y - 1):
            # 建立八邻域的数组,围绕中心像素点,按顺时针标记八个像素点
            eight_neighbor1 = np.zeros(8, dtype=Pixel_type)
            eight_neighbor1[0] = b[i - 1][j - 1]
            eight_neighbor1[1] = b[i - 1][j]
            eight_neighbor1[2] = b[i - 1][j + 1]
            eight_neighbor1[3] = b[i][j + 1]
            eight_neighbor1[4] = b[i + 1][j + 1]
            eight_neighbor1[5] = b[i + 1][j]
            eight_neighbor1[6] = b[i + 1][j - 1]
            eight_neighbor1[7] = b[i][j - 1]
            # 划分方法一
            eight_neighbor_division1 = division_method1(eight_neighbor1)
            # 建立八邻域的数组,围绕中心像素点,按顺时针标记八个像素点
            eight_neighbor2 = np.zeros(8, dtype=Pixel_type)
            eight_neighbor2[0] = b[i - 1][j - 1]
            eight_neighbor2[1] = b[i - 1][j]
            eight_neighbor2[2] = b[i - 1][j + 1]
            eight_neighbor2[3] = b[i][j + 1]
            eight_neighbor2[4] = b[i + 1][j + 1]
            eight_neighbor2[5] = b[i + 1][j]
            eight_neighbor2[6] = b[i + 1][j - 1]
            eight_neighbor2[7] = b[i][j - 1]
            # 划分方法二
            eight_neighbor_division2 = division_method2(eight_neighbor2)
            # 判断区分度
            if judge_division_degree(eight_neighbor_division1) and \
                    judge_division_degree(eight_neighbor_division2):
                # 找出划分区域不一样的像素点
                for k in range(0, 8):
                    if eight_neighbor_division1[k]['part'] != eight_neighbor_division2[k]['part']:
                        if k == 0:
                            b[i - 1][j - 1]['noise'] += 1
                        elif k == 1:
                            b[i - 1][j]['noise'] += 1
                        elif k == 2:
                            b[i - 1][j + 1]['noise'] += 1
                        elif k == 3:
                            b[i][j + 1]['noise'] += 1
                        elif k == 4:
                            b[i + 1][j + 1]['noise'] += 1
                        elif k == 5:
                            b[i + 1][j]['noise'] += 1
                        elif k == 6:
                            b[i + 1][j - 1]['noise'] += 1
                        elif k == 7:
                            b[i][j - 1]['noise'] += 1
    return b

# 以 BGR 方式读入图片
img = cv.imread('test3.png', 1)
# 检查读入是否成功
# print(img.shape)
# 输出(302, 302, 3),代表成功

# 拆分图像通道
B, G, R = cv.split(img)

# 调用 noise_check() 函数,检查可能的噪声点
b_px = noise_check(B)  # 以 B 通道举例

# 在 output。txt 文件下输出噪声值的矩阵排布
pf = open('output.txt', 'w')
for i1 in range(0, 302):
    for j1 in range(0, 302):
        print(b_px[i1][j1]['noise'], end='', file=pf)
    print('', file=pf)
print(" 导出成功 ")
正文完
 0
icvuln
版权声明:本站原创文章,由 icvuln 于2021-02-28发表,共计7868字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
评论(没有评论)